/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/// <reference path="../worker-group.ts" />

namespace lcc {

const TAG = "lcc/worker/thread/thread-group.ts";

const { Worker } = require('worker_threads');

/**
 * @zh
 * 线程工作组
 */
export class ThreadGroup extends WorkerGroup {

	/**
	 * @zh
	 * 工作线程对象
	 */
	private _worker:any = null;

	/**
	 * @zh
	 * 环境脚本,可以导出,load():Promise<void>和unload():Promise<void>用于创建和销毁
	 */
	private _envScript:string = null;

	/**
	 * @zh
	 * 调用索引
	 */
	private _callIndex:number = 0;

	/**
	 * 回调映射表
	 */
	private _returnMap:{[key:number]:(result:any)=>void} = js.createMap();

	/**
	 * 工作者名称数组
	 */
	private _workerNames:string[] = null;

	/**
	 * @zh
	 * 构造线程工作组
	 * 
	 * @param name 组名称
	 * @param priority 优先级
	 * @param envScript 环境脚本
	 */
	constructor(name:string, priority:number, envScript:string, workerNames:string[]){
		super(name, priority, workerNames);
		this._workerNames = workerNames;
		if(IS_MASTER){
			this._envScript = envScript;
			this._worker = new Worker(__filename);
			this._worker.on('message', this.onWorkerMessage.bind(this));
			this._worker.on('error', this.onWorkerError.bind(this));
			this._worker.on('exit', this.onWorkerExit.bind(this));
		}else{
			let msg = `must be executed in master!`;
			logMgr.error(TAG, msg);
			throw new Error(`${TAG} - ${msg}`);
		}
	}

	/**
	 * @zh
	 * 发送线程消息
	 * 
	 * @param type - 消息类型
	 * @param args - 消息参数
	 */
	private callSubWorker(type:string, ...args:any[]){
		return new Promise((resolve)=>{
			if(this._worker){
				let index = this._callIndex ++;
				//console.log('callSubWorker','__call__', index , type, args);
				this._worker.postMessage([ '__call__', index , type, args ]);
				this._returnMap[index] = (result)=>{
					resolve(result);
				};
			}else{
				logMgr.error(TAG, `${this.getName()} thread is null!`);
				resolve(null);
			}
		});
	}
	
	/**
	 * @zh
	 * 当收到线程消息
	 * 
	 * @param values - 消息对象数组
	 */
	private async onWorkerMessage(values:any[]){
		if(this._worker){
			let call = values[0];
			if(call == '__call__'){
				let index = values[1]; 
				let type = values[2]; 
				let args = values[3]; 
				let callFun = __onWorkerCallMap[type];
				if(callFun){
					let result = null;
					try {
						result = await callFun(...args);
					}catch(e){
						logMgr.error(TAG, `onWorkerMessage __call__ error ${type} ${e}`);
					}
					this._worker.postMessage([ '__return__', index, result ]);
				}else{
					this._worker.postMessage([ '__return__', index, null ]);
				}
			}else if(call == '__return__'){
				let index = values[1];
				let returnFun = this._returnMap[index];
				if(returnFun){
					delete this._returnMap[index];
					try {
						returnFun(values[2]);
					}catch(e){
						logMgr.error(TAG, `onWorkerMessage __return__ error ${index} ${e}`);
					}
				}
			}
		}
	}

	/**
	 * @zh
	 * 当收到线程错误
	 * 
	 * @param error - 错误
	 */
	private onWorkerError(error:Error){
		logMgr.error(TAG, `${this.getName()} error : ${error.message}`);
		this._worker = null;
		super.destroy();
	}

	/**
	 * @zh
	 * 当收到线程退出
	 * 
	 * @param exitCode - 退出码
	 */
	private onWorkerExit(exitCode:number){
		DEBUG.WORKER && logMgr.info(TAG, `${this.getName()} exit: ${exitCode}`);
		this._worker = null;
		super.destroy();
	}

	/**
	 * @zh
	 * 安装心跳
	 * 
	 * @internal
	 */
	protected setupHeartTick(){}

	/**
	 * @zh
	 * 删除心跳
	 * 
	 * @internal
	 */
	protected deleteHeartTick(){}

	/**
	 * @zh
	 * 初始化工作组
	 */
	public async initialize(){
		this._setState(GroupState.INITIALIZING);
		if(this._worker){
			await this.callSubWorker('__initialize__', this.getName(), this._envScript, this._workerNames);
		}
		this._setState(GroupState.WORKING);
	}
	
    /**
     * @zh
     * 启动工作者
     * 
     * @param workerMeta - 工作者元数据。
     */
	public async startWorker(workerMeta:WorkerMeta){
        if(this.getState() != GroupState.WORKING){
			logMgr.warn(TAG, `startWorker '${this.getName()}-${workerMeta.id.workerName}-${workerMeta.id.instanceName}' group does not work`);
            return false;
        }
		return <boolean>await this.callSubWorker('startWorker', workerMeta);
	}

    /**
     * @zh
     * 停止工作者
     * 
     * @param workerIDorMeta - 工作者标识或者工作者元数据。如果为字符串，则表示工作者名称，所属工作组默认为当前工作组
     * @param soft - 软停止
     */
    public async stopWorker(workerIDorMeta:string|WorkerIdentifier|WorkerMeta, soft:boolean = false){
		let workerMeta = optionWorkerMeta(workerIDorMeta);
		return <boolean>await this.callSubWorker('stopWorker', workerMeta, soft);
	}
	
    /**
     * @zh
     * 调用工作者
     * 
     * @param workerIDorMeta - 工作者标识或者工作者元数据。如果为字符串，则表示工作者名称，所属工作组默认为当前工作组
     * @param type - 调用类型
     * @param args - 调用参数
     */
    public async callWorker(workerIDorMeta:string|WorkerIdentifier|WorkerMeta, type:string, ...args:any[]){
		let workerMeta = optionWorkerMeta(workerIDorMeta);
        if(this.getState() != GroupState.WORKING){
			logMgr.warn(TAG, `startWorker '${this.getName()}-${workerMeta.id.workerName}-${workerMeta.id.instanceName}' group does not work`);
            return {
                error : WorkerCallError.GROUP_NOT_WORK
            }
        }
		return <WorkerCallResult>await this.callSubWorker('callWorker', workerMeta, type, ...args);
	}
	
    /**
     * @zh
     * 通知所有工作者
     * 
     * @param type 调用类型
     * @param args 调用参数
     */
	public async notifyAll(type:string, ...args:any[]){
        if(this.getState() != GroupState.WORKING){
			return logMgr.warn(TAG, `notifyAll '${this.getName()}' group does not work`);
        }
		
        await this.callSubWorker('notifyAll', type, ...args);
    }
    
    /**
     * @zh
     * 销毁工作组
     */
    public async destroy(){
		await super.destroy();
		if(this._worker){
			await this.callSubWorker('__destroy__', this._envScript);
			await this._worker.terminate();
			this._worker = null;
		}
	}
}

}
